package phase6;

import java.io.File;
import java.io.ObjectStreamException;
import java.io.Serializable;
import java.util.HashMap;
import java.util.Scanner;

import cs340.stormtroopers.hypeerweb.HyPeerWeb;
import cs340.stormtroopers.hypeerweb.Node;
import cs340.stormtroopers.hypeerweb.NodeProxy;

import phase6.PortNumber;

public class SegmentRunner implements Serializable
{
	private static final long serialVersionUID = 3162345329213472691L;

	public static final int DEFAULT_PORT = 49209;

	private static final LocalObjectId SINGLETON_LOCAL_OBJECT_ID = new LocalObjectId(
			GlobalLocalObjectIds.SEGMENTRUNNER_SINGLETON);

	private final HyPeerWeb web;
	private SegmentRunner next;
	private SegmentRunner previous;

	private int savedPortNumber;
	private boolean shutdownHasBeenCalledOnMe;

	private SegmentRunner(int portNo)
	{
		web = HyPeerWeb.getSingleton();
		ObjectDB.getSingleton().store(HyPeerWeb.getLocalObjectId(), web);
		next = null;
		previous = null;
		shutdownHasBeenCalledOnMe = false;
		savedPortNumber = portNo;
	}

	// for use with the proxy
	protected SegmentRunner(boolean ignoreMe)
	{
		web = null;
		next = null;
		previous = null;
		shutdownHasBeenCalledOnMe = false;
		savedPortNumber = -1;
	}

	private Object writeReplace() throws ObjectStreamException
	{
		if (!shutdownHasBeenCalledOnMe)
			return new SegmentRunnerProxy(new GlobalObjectId(
				SINGLETON_LOCAL_OBJECT_ID));
		else
			return this;
	}

	/**
	 * Used to start a HyPeerWeb segment from the command line.<br/>
	 * Note: the arguments are as follows: [<port number>] [-o <IP or hostname
	 * of segment to connect to> <port of segment to connect to>]
	 * 
	 * @pre none
	 * @post a HyPeerWeb segment is running on the port specified in args[0] or
	 *       the default
	 * @param args
	 *            command-line arguments
	 */
	public static void main(String[] args)
	{
//		ProxyConstructor.create("phase6.SegmentRunner");

		//////////////////////////////// PARSE ARGS
		int argIdx = 0;

		int port;
		if (args.length == 0 || !validPortNumber(args[0]))
			port = DEFAULT_PORT;
		else
		{
			port = Integer.parseInt(args[0]);
			argIdx++;
		}

		String otherHostname = null;
		String otherPortNo = null;
		if (argIdx < args.length && args[argIdx].equals("-o"))
		{
			otherHostname = args[argIdx + 1];
			otherPortNo = args[argIdx + 2];
		}
		
		/////////////////////////////// LOAD SEGMENT RUNNER FROM DISK OR CREATE

		SegmentRunner main = null;
		File f = new File(getFilename(port));
		if (f.exists())
		{
			try
			{
				System.out.println("Trying to open " + f.getPath()
						+ "...");

				ObjectDB.getSingleton().restore(f.getPath());
				main = (SegmentRunner)ObjectDB.getSingleton().getValue(SINGLETON_LOCAL_OBJECT_ID);
				main.shutdownHasBeenCalledOnMe = false;
				main.web.setNodesShouldSerializeToProxies(true);
				main.run();
				
				System.out.println("Successfully loaded and started segment from file");
			}
			catch (Exception e)
			{
				//System.out.println(e.getMessage());
				System.err.println("Could not load from " + f.getPath());
				System.err.println("Reason: " + e.getMessage());
				e.printStackTrace(System.err);
				System.exit(1);
			}
		}
		else
		{
			//////////////////////////////// no stored hypeerweb - create a new one
			System.out.println("Starting a new HyPeerWeb Segment on port " + port);
			main = new SegmentRunner(port);
			ObjectDB.getSingleton().store(SINGLETON_LOCAL_OBJECT_ID, main);
			main.run();
			System.out.println("Segment started...");
	
			if (otherHostname != null)
			{
				System.out.println("Connecting to existing segment...");
				
				GlobalObjectId otherSegmentId = new GlobalObjectId(otherHostname,
						new PortNumber(Integer.parseInt(otherPortNo)),
						SINGLETON_LOCAL_OBJECT_ID);
				SegmentRunnerProxy other = new SegmentRunnerProxy(otherSegmentId);
				main.previous = other;
				main.next = other.insertNewSegment(main);
	
				// get node from other segment and insert our own node
				Node newNode = new Node(42);
				Node startNode = other.getRandomNode();
	
				System.out.println("Node from the other segment: "
						+ startNode.toString());
	
				startNode.addToHyPeerWeb(newNode);
				// main.web.addNode(newNode);
	
				System.out.println("Node from the other segment AFTER add: "
						+ startNode.toString());
	
				System.out.println("Got a node from the other segment with webId "
						+ startNode.getWebIdValue());
				System.out.println("Added a node to my own segment with id "
						+ newNode.getWebIdValue());
				System.out.println("Total number of nodes now in my segment: "
						+ main.web.size());
				//ObjectDB.getSingleton().dump();
			}
			else
			{
				System.out
						.println("Not connecting to another segment, adding node 0...");
				main.web.addAnotherNode();
				//ObjectDB.getSingleton().dump();
			}
		}

		Scanner scanner = new Scanner(System.in);
		while (true)
			if (scanner.hasNext() && scanner.next().equals("close"))
				main.shutdownAndWriteToDisk();
	}

	public void shutdownAndWriteToDisk()
	{
		if (!shutdownHasBeenCalledOnMe)
		{
			shutdownHasBeenCalledOnMe = true;
			System.out.println("CLOSING: " + getSavedPortNumber());
			web.setNodesShouldSerializeToProxies(false);
			web.clearGUIs();
			ObjectDB.getSingleton().save(getFilename(getSavedPortNumber()));

			if (next != null)
				next.shutdownAndWriteToDisk();

			System.exit(0);
		}
		//return true;
	}

	/**
	 * Used to insert a new segment into the ring of SegmentRunners.
	 * 
	 * @param newSegment
	 * @post this.next = newSegment AND<br/>
	 *       result = the SegmentRunner which newSegment will assign to its next
	 *       value<br/>
	 *       i.e. newSegment.next = result
	 */
	public SegmentRunner insertNewSegment(SegmentRunner newSegment)
	{
		System.out.println("Adding a new segment into the ring...");
		SegmentRunner nextOld = next;
		next = newSegment;

		// if we don't have another segment that follows us, it should point
		// back to us
		if (nextOld == null)
		{
			previous = newSegment;
			return this;
		}
		else
		{
			nextOld.setPreviousSegment(newSegment);
			return nextOld;
		}
	}

	public boolean setPreviousSegment(SegmentRunner prevSegment)
	{
		previous = prevSegment;
		return true;
	}
	
	public boolean setNextSegment(SegmentRunner nextSegment)
	{
		next = nextSegment;
		return true;
	}

	public Node getRandomNode()
	{
		return web.getRandomNode();
	}

	private void run()
	{
		PortNumber.setApplicationsPortNumber(new PortNumber(savedPortNumber));
		PeerCommunicator.createPeerCommunicator(new PortNumber(savedPortNumber));
	}

	private static boolean validPortNumber(String port)
	{
		for (int i = 0; i < port.length(); ++i)
			if (!Character.isDigit(port.charAt(i)))
				return false;
		return true;
	}

	public int getSavedPortNumber()
	{
		return savedPortNumber;
	}

	public static String getFilename(int portNo)
	{
		return System.getProperty("user.home") + "/CS340HyPeerWebData/Segment_" + portNo+".dump";
	}
	
	public boolean moveNodesToAnotherSegment(HashMap<Integer, Node> nodes)
	{
		next.acceptNodes(nodes);
		
		//Re-forge the ring without me
		previous.setNextSegment(next);
		next.setPreviousSegment(previous);
		return true;
	}
	
	public boolean acceptNodes(HashMap<Integer, Node> nodes)
	{
		for(Node n : nodes.values())
		{
			if(n instanceof NodeProxy)
			{
				System.err.println("Shouldn't happen");
			}
			LocalObjectId localId = new LocalObjectId();
			n.setLocalObjectId(localId);
			ObjectDB.getSingleton().store(n.getLocalObjectId(), n);
			
			
			web.addNode(n);
			
			GlobalObjectId GlobalId = new GlobalObjectId(n.getLocalObjectId());
			
			//Update any neighbors that may point to me
			for(Node nodeConn : n.getConnections())
			{
				for(Node nodeConnConn : nodeConn.getConnections())
				{
					if(nodeConnConn.getWebIdValue() == n.getWebIdValue())
					{
						if(nodeConnConn instanceof NodeProxy)
						{
							((NodeProxy) nodeConnConn).setGlobalObjectId(GlobalId);
						}
					}
				}
			}
			
			//update any folds that may point to me 
			
		}
		System.out.println(web.toString());
		ObjectDB.getSingleton().dump();
		
		return true;
	}
}
